{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Ic4_occAAiAT"
      },
      "source": [
        "##### Copyright 2020 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "ioaprt5q5US7"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "sPrjeJhFQBmu"
      },
      "source": [
        "# Интегрированные градиенты"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hKY4XMc9o8iB"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://www.tensorflow.org/tutorials/interpretability/integrated_gradients\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />Смотрите на TensorFlow.org</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ru/tutorials/interpretability/integrated_gradients.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Запустите в Google Colab</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ru/tutorials/interpretability/integrated_gradients.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />Изучайте код на GitHub</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ru/tutorials/interpretability/integrated_gradients.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\" />Скачайте ноутбук</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a href=\"https://tfhub.dev/google/imagenet/inception_v1/classification/4\"><img src=\"https://www.tensorflow.org/images/hub_logo_32px.png\" />Смотрите модели TF Hub</a>\n",
        "  </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8a2322303a3f"
      },
      "source": [
        "Note: Вся информация в этом разделе переведена с помощью русскоговорящего Tensorflow сообщества на общественных началах. Поскольку этот перевод не является официальным, мы не гарантируем что он на 100% аккуратен и соответствует [официальной документации на английском языке](https://www.tensorflow.org/?hl=en). Если у вас есть предложение как исправить этот перевод, мы будем очень рады увидеть pull request в [tensorflow/docs](https://github.com/tensorflow/docs) репозиторий GitHub. Если вы хотите помочь сделать документацию по Tensorflow лучше (сделать сам перевод или проверить перевод подготовленный кем-то другим), напишите нам на [docs-ru@tensorflow.org list](https://groups.google.com/a/tensorflow.org/forum/#!forum/docs-ru)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NG17_Wp6ikKf"
      },
      "source": [
        "В этом руководстве показано, как реализовать **Integrated Gradients(IG)**, метод [Объясняемого искусственного интеллекта](https://en.wikipedia.org/wiki/Explainable_artificial_intelligence), представленный в статье [Axiomatic Attribution for Deep Networks](https://arxiv.org/abs/1703.01365). `IG` стремится объяснить взаимосвязь между предсказаниями модели с точки зрения ее характеристик. Он имеет множество вариантов использования, включая понимание важности функций, определение перекоса данных и отладку эффективности модели.\n",
        "\n",
        "`IG` стал популярным методом интерпретируемости из-за его широкой применимости к любой дифференцируемой модели (например изображение, текст, структурированные данные), простоты реализации, теоретического обоснования и вычислительной эффективности по сравнению с альтернативными подходами, которые позволяют масштабировать его до крупных сетей и пространств признаков, таких как изображения.\n",
        "\n",
        "В этом руководстве вы шаг за шагом пройдете через реализацию `IG`, чтобы понять важность признаков пикселей для классификатора изображений. В качестве примера рассмотрим это [изображение](https://commons.wikimedia.org/wiki/File:San_Francisco_fireboat_showing_off.jpg) с пожарным катером, распыляющим струи воды. Вы бы классифицировали это изображение как пожарную лодку и могли бы выделить пиксели, из которых состоит лодка и водометы, как важные для вашего решения. Позже в этом руководстве ваша модель также классифицирует это изображение как пожарную лодку, однако определеяет ли она те же самые пиксели как важные, при объяснении своего решения?\n",
        "\n",
        "На изображениях ниже под заголовками `IG Attribution Mask` и `Original + IG Mask Overlay` вы можете видеть, что ваша модель в своем решении выделяет(фиолетовым цветом) пиксели, составляющие водометы и струи воды, как более важные, чем сама лодка. Как ваша модель будет обобщать новые пожарные лодки? А как насчет пожарных катеров без водометов? Читайте дальше, чтобы узнать о том, как работает `IG` и как применить `IG` к вашим моделям.\n",
        "\n",
        "![изображение 1](images/IG_fireboat.png)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ppydw6ZbKzM1"
      },
      "source": [
        "## Установка"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "cbUMIubipgg0"
      },
      "outputs": [],
      "source": [
        "import matplotlib.pylab as plt\n",
        "import numpy as np\n",
        "import tensorflow as tf\n",
        "import tensorflow_hub as hub"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "GVVV4BGrABkA"
      },
      "source": [
        "### Загрузка предварительно обученного классификатора изображений с TF-Hub"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7kwwJ35xmtoK"
      },
      "source": [
        "IG может применяться к любой дифференцируемой модели. Следуя оригинальной статье, вы будете использовать предварительно обученную версию такой же модели, Inception V1, которую вы загрузите с [TensorFlow Hub](https://tfhub.dev/google/imagenet/inception_v1/classification/4)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "14APZcfHolKj"
      },
      "outputs": [],
      "source": [
        "model = tf.keras.Sequential([\n",
        "    hub.KerasLayer(\n",
        "        name='inception_v1',\n",
        "        handle='https://tfhub.dev/google/imagenet/inception_v1/classification/4',\n",
        "        trainable=False),\n",
        "])\n",
        "model.build([None, 224, 224, 3])\n",
        "model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "GjLRn2e5xFOb"
      },
      "source": [
        "Работая с этим руководством, вам необходимо помнить следующее о Inception V1:\n",
        "\n",
        "**Входные данные**: ожидаемая размерность входных данных для модели - `(None, 224, 224, 3)`. Это 4D тензор содержащий данные типа `float32` и имеющий размерность `(batch_size, height, width, RGB channels)`, элементы которого представляют собой значения RGB цветов пикселей, нормализованные в диапазоне [0, 1]. Первый элемент - `None` показывает, что модель может принимать любой целочисленный размер пакета.\n",
        "\n",
        "**Выходы**: `tf.Tensor` размерности `(batch_size, 1001)`. Каждая строка представляет собой прогнозируемую оценку модели для каждого из 1001 класса из ImageNet. Для получения индекса предсказанного класса, имеющего наибольшее значение вероятности, вы можете использовать `tf.argmax(predictions, axis = -1)`. Кроме того, вы также можете преобразовать полный вывод модели в предсказанные вероятности для всех классов, используя `tf.nn.softmax(predictions, axis = -1)`, например для количественной оценки неопределенности модели, или для исследования предсказанных классов в процессе отладки."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "huZnb_O0L9mw"
      },
      "outputs": [],
      "source": [
        "def load_imagenet_labels(file_path):\n",
        "  labels_file = tf.keras.utils.get_file('ImageNetLabels.txt', file_path)\n",
        "  with open(labels_file) as reader:\n",
        "    f = reader.read()\n",
        "    labels = f.splitlines()\n",
        "  return np.array(labels)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Rtrl-u7T6NEk"
      },
      "outputs": [],
      "source": [
        "imagenet_labels = load_imagenet_labels('https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "STpIJr1Z5r_u"
      },
      "source": [
        "### Загрузка и предварительная обработка изображений с помощью `tf.image`\n",
        "\n",
        "Вы протестируете IG, используя два изображения из [Wikimedia Commons](https://commons.wikimedia.org/wiki/Main_Page): \n",
        "[Пожарный катер](https://commons.wikimedia.org/wiki/File:San_Francisco_fireboat_showing_off.jpg ) и \n",
        "[Гигантская панда](https://commons.wikimedia.org/wiki/File:Giant_Panda_2.JPG)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "YOb0Adq-rU5J"
      },
      "outputs": [],
      "source": [
        "def read_image(file_name):\n",
        "  image = tf.io.read_file(file_name)\n",
        "  image = tf.image.decode_jpeg(image, channels=3)\n",
        "  image = tf.image.convert_image_dtype(image, tf.float32)\n",
        "  image = tf.image.resize_with_pad(image, target_height=224, target_width=224)\n",
        "  return image"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "_khLTN75CLMJ"
      },
      "outputs": [],
      "source": [
        "img_url = {\n",
        "    'Fireboat': 'http://storage.googleapis.com/download.tensorflow.org/example_images/San_Francisco_fireboat_showing_off.jpg',\n",
        "    'Giant Panda': 'http://storage.googleapis.com/download.tensorflow.org/example_images/Giant_Panda_2.jpeg',\n",
        "}\n",
        "\n",
        "img_paths = {name: tf.keras.utils.get_file(name, url) for (name, url) in img_url.items()}\n",
        "img_name_tensors = {name: read_image(img_path) for (name, img_path) in img_paths.items()}"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "AYIeu8rMLN-8"
      },
      "outputs": [],
      "source": [
        "plt.figure(figsize=(8, 8))\n",
        "for n, (name, img_tensors) in enumerate(img_name_tensors.items()):\n",
        "  ax = plt.subplot(1, 2, n+1)\n",
        "  ax.imshow(img_tensors)\n",
        "  ax.set_title(name)\n",
        "  ax.axis('off')\n",
        "plt.tight_layout()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QvTYc8IZKbeO"
      },
      "source": [
        "### Классификация изображений\n",
        "Начнем с классификации этих изображений и отображения трех самых надежных прогнозов. Ниже приводится вспомогательная функция для получения топ k прогнозируемых меток и вероятностей."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5gsO7ILHZ0By"
      },
      "outputs": [],
      "source": [
        "def top_k_predictions(img, k=3):\n",
        "  image_batch = tf.expand_dims(img, 0)\n",
        "  predictions = model(image_batch)\n",
        "  probs = tf.nn.softmax(predictions, axis=-1)\n",
        "  top_probs, top_idxs = tf.math.top_k(input=probs, k=k)\n",
        "  top_labels = imagenet_labels[tuple(top_idxs)]\n",
        "  return top_labels, top_probs[0]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "l80a8N2vcIP-"
      },
      "outputs": [],
      "source": [
        "for (name, img_tensor) in img_name_tensors.items():\n",
        "  plt.imshow(img_tensor)\n",
        "  plt.title(name, fontweight='bold')\n",
        "  plt.axis('off')\n",
        "  plt.show()\n",
        "\n",
        "  pred_label, pred_prob = top_k_predictions(img_tensor)\n",
        "  for label, prob in zip(pred_label, pred_prob):\n",
        "    print(f'{label}: {prob:0.1%}')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "v-lH1N4timM2"
      },
      "source": [
        "## Расчет интегрированных градиентов"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "FV7pfZHANaz1"
      },
      "source": [
        "Ваша модель, Inception V1, представляет собой обученную функцию, которая описывает связи между входными признаками, значениями пикселей изображения и выходными данными, определяемыми величиной вероятности между 0 и 1. Ранние методы интерпретируемости для нейронных сетей определяют важность признаков используя градиенты, которые говорят вам, какие пиксели имеют самую большую величину прогноза вашей модели в данной точке функции прогнозирования. Однако градиенты описывают только *локальные* изменения функции прогнозирования и не описывают полностью всю функцию. По мере того как ваша модель «изучает» взаимосвязи между отдельным пикселем и правильным классом ImageNet, градиент для этого пикселя будет *понижаться*, то есть становиться все более маленьким и даже приближаться к нулю. Рассмотрим простую функцию модели:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0AUkIUvpkaO8"
      },
      "outputs": [],
      "source": [
        "def f(x):\n",
        "  \"\"\"Упрощенная функция модели.\"\"\"\n",
        "  return tf.where(x < 0.8, x, 0.8)\n",
        "\n",
        "def interpolated_path(x):\n",
        "  \"\"\"Прямой путь.\"\"\"\n",
        "  return tf.zeros_like(x)\n",
        "\n",
        "x = tf.linspace(start=0.0, stop=1.0, num=6)\n",
        "y = f(x)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "kMdAKooulVRE"
      },
      "outputs": [],
      "source": [
        "fig = plt.figure(figsize=(12, 5))\n",
        "ax0 = fig.add_subplot(121)\n",
        "ax0.plot(x, f(x), marker='o')\n",
        "ax0.set_title('Gradients saturate over F(x)', fontweight='bold')\n",
        "ax0.text(0.2, 0.5, 'Gradients > 0 = \\n x is important')\n",
        "ax0.text(0.7, 0.85, 'Gradients = 0 \\n x not important')\n",
        "ax0.set_yticks(tf.range(0, 1.5, 0.5))\n",
        "ax0.set_xticks(tf.range(0, 1.5, 0.5))\n",
        "ax0.set_ylabel('F(x) - model true class predicted probability')\n",
        "ax0.set_xlabel('x - (pixel value)')\n",
        "\n",
        "ax1 = fig.add_subplot(122)\n",
        "ax1.plot(x, f(x), marker='o')\n",
        "ax1.plot(x, interpolated_path(x), marker='>')\n",
        "ax1.set_title('IG intuition', fontweight='bold')\n",
        "ax1.text(0.25, 0.1, 'Accumulate gradients along path')\n",
        "ax1.set_ylabel('F(x) - model true class predicted probability')\n",
        "ax1.set_xlabel('x - (pixel value)')\n",
        "ax1.set_yticks(tf.range(0, 1.5, 0.5))\n",
        "ax1.set_xticks(tf.range(0, 1.5, 0.5))\n",
        "ax1.annotate('Baseline', xy=(0.0, 0.0), xytext=(0.0, 0.2),\n",
        "             arrowprops=dict(facecolor='black', shrink=0.1))\n",
        "ax1.annotate('Input', xy=(1.0, 0.0), xytext=(0.95, 0.2),\n",
        "             arrowprops=dict(facecolor='black', shrink=0.1))\n",
        "plt.show();"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LswhGFd0xZBr"
      },
      "source": [
        "* **слева**: градиенты вашей модели для пикселя `x` положительные в диапазоне от 0,0 до 0,8, и равные 0,0 в диапазоне от 0,8 до 1,0. Пиксель `x` явно оказывает значительное влияние на приближение вашей модели к 80% точности прогнозирования. *Имеет ли значение, что важность пикселя `x` мала или непостоянна?*\n",
        "\n",
        "* **справа**: Интуиция, лежащая в основе IG, накапливает локальные градиенты пикселя `x` для оценки его важности, чтобы определить, сколько он добавляет или вычитает из общей вероятности выходного класса вашей модели. Вы можете разбить вычисление IG на 3 части:\n",
        "  1. интерполировать небольшие шаги по прямой линии в пространстве функций между 0(базовая линия или начальная точка) и 1(значение входного пикселя)\n",
        "  2. вычислять градиенты на каждом шаге между предсказаниями вашей модели относительно каждого шага\n",
        "  3. аппроксимировать интеграл между базовой линией и входными данными путем накопления(совокупного среднего) этих локальных градиентов.\n",
        "\n",
        "Чтобы усилить эту интуицию, вы пройдете через эти 3 части, применив IG к изображению \"Пожарный катер\"."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "28MU35BCLM-s"
      },
      "source": [
        "### Установка базового уровня"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MIPG5yYfkydQ"
      },
      "source": [
        "Базовая линия - это входное изображение, используемое в качестве отправной точки для расчета важности признаков. Интуитивно вы можете думать о базовой линии как о представлении влияния каждого пикселя на прогноз «пожарной лодки», в случае его отсутствия или присутствия во входном изображении. В результате выбор базовой линии играет центральную роль в интерпретации и визуализации важности признаков пикселя. Дополнительное обсуждение выбора базовой линии см. в разделе «Дальнейшие шаги» в нижней части этого руководства. Здесь вы будете использовать полностью черное изображение, все значения пикселей которого равны нулю.\n",
        "\n",
        "Другие варианты, с которыми вы можете поэкспериментировать, включают полностью белое изображение или случайное изображение, которое вы можете создать с помощью метода `tf.random.uniform(shape = (224,224,3), minval = 0.0, maxval = 1.0)`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "wxvpwGkj4G4J"
      },
      "outputs": [],
      "source": [
        "baseline = tf.zeros(shape=(224,224,3))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "vXRYwBWQS19B"
      },
      "outputs": [],
      "source": [
        "plt.imshow(baseline)\n",
        "plt.title(\"Baseline\")\n",
        "plt.axis('off')\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xphryu2mGAk8"
      },
      "source": [
        "### Перевод формул в код"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QstMR0IcbfFA"
      },
      "source": [
        "Формула для интегрированных градиентов выглядит следующим образом:\n",
        "\n",
        "$IntegratedGradients_{i}(x) ::= (x_{i} - x'_{i})\\times\\int_{\\alpha=0}^1\\frac{\\partial F(x'+\\alpha \\times (x - x'))}{\\partial x_i}{d\\alpha}$\n",
        "\n",
        "где:\n",
        "\n",
        "$_{i}$ = признак   \n",
        "$x$ = вход  \n",
        "$x'$ = базовая линия   \n",
        "$\\alpha$ = константа для создания шума"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_7w8SD5YMqvi"
      },
      "source": [
        "На практике вычисление определенного интеграла не всегда возможно численно и может быть затратным в вычислительном отношении, поэтому вы вычисляете следующее численное приближение:\n",
        "\n",
        "$IntegratedGrads^{approx}_{i}(x)::=(x_{i}-x'_{i})\\times\\sum_{k=1}^{m}\\frac{\\partial F(x' + \\frac{k}{m}\\times(x - x'))}{\\partial x_{i}} \\times \\frac{1}{m}$\n",
        "\n",
        "где:\n",
        "\n",
        "$_{i}$ = признак (единичный пиксель)  \n",
        "\n",
        "$x$ = вход (тензор с изображением)  \n",
        "\n",
        "$x'$ = baseline (тензор с изображением)  \n",
        "\n",
        "$k$ = константа для создания шума\n",
        "\n",
        "$m$ = количество шагов в приближении суммы Римана\n",
        "\n",
        "$(x_{i}-x'_{i})$ = условие для отличия от базовой линии. Это необходимо для масштабирования интегрированных градиентов и сохранения их в соответствии с исходным изображением. Путь от базового изображения до входа находится в пиксельном пространстве. Поскольку с IG вы интегрируете по прямой(линейное преобразование), это в конечном итоге примерно эквивалентно интегральному члену производной функции интерполированного изображения по отношению к $\\alpha$ с необходимым количеством шагов. Интеграл суммирует градиент каждого пикселя, умноженный на изменение пикселя вдоль пути. Проще реализовать эту интеграцию в виде единых шагов от одного изображения к другому, подставив $x: = (x'+ \\alpha (x-x'))$. Таким образом, замена переменных дает $dx = (x-x')d\\alpha $. Член $(x-x')$ является постоянным и не учитывается в интеграле."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5aPG68RssS2h"
      },
      "source": [
        "### Интерполяция изображения"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pPrldEYsIR4M"
      },
      "source": [
        "$IntegratedGrads^{approx}_{i}(x)::=(x_{i}-x'_{i})\\times\\sum_{k=1}^{m}\\frac{\\partial F(\\overbrace{x' + \\frac{k}{m}\\times(x - x')}^\\text{interpolate m images at k intervals})}{\\partial x_{i}} \\times \\frac{1}{m}$"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "y4r-ZrIIsdbI"
      },
      "source": [
        "Сначала вы создадите [линейную интерполяцию](https://en.wikipedia.org/wiki/Linear_interpolation) между базовой линией и исходным изображением. Вы можете думать об интерполированных изображениях как о небольших шагах в пространстве признаков между базовой линией и вводом, представленными $\\alpha$ в исходном уравнении."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "I42mBKXyjcIc"
      },
      "outputs": [],
      "source": [
        "m_steps=50\n",
        "# Сгенерируем интервалы m_steps для integral_approximation() ниже.\n",
        "alphas = tf.linspace(start=0.0, stop=1.0, num=m_steps+1) "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "7SWLSFOHsbgh"
      },
      "outputs": [],
      "source": [
        "def interpolate_images(baseline,\n",
        "                       image,\n",
        "                       alphas):\n",
        "  alphas_x = alphas[:, tf.newaxis, tf.newaxis, tf.newaxis]\n",
        "  baseline_x = tf.expand_dims(baseline, axis=0)\n",
        "  input_x = tf.expand_dims(image, axis=0)\n",
        "  delta = input_x - baseline_x\n",
        "  images = baseline_x +  alphas_x * delta\n",
        "  return images"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "s4zFzbUBj684"
      },
      "source": [
        "Давайте используем ф-цию interpolate_images() для генерации интерполированных изображений по линейному пути с альфа-интервалами между черным базовым изображением и примером изображения «Fireboat»."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "NgVx8swDQtTl"
      },
      "outputs": [],
      "source": [
        "interpolated_images = interpolate_images(\n",
        "    baseline=baseline,\n",
        "    image=img_name_tensors['Fireboat'],\n",
        "    alphas=alphas)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QABFsuCvkO1h"
      },
      "source": [
        "Визуализируем интерполированные изображения. \n",
        "\n",
        "Примечание. Другой способ представить константу $\\alpha$ состоит в том, что она последовательно увеличивает интенсивность каждого интерполированного изображения."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "9tmBGdnHAupk"
      },
      "outputs": [],
      "source": [
        "fig = plt.figure(figsize=(20, 20))\n",
        "\n",
        "i = 0\n",
        "for alpha, image in zip(alphas[0::10], interpolated_images[0::10]):\n",
        "  i += 1\n",
        "  plt.subplot(1, len(alphas[0::10]), i)\n",
        "  plt.title(f'alpha: {alpha:.1f}')\n",
        "  plt.imshow(image)\n",
        "  plt.axis('off')\n",
        "\n",
        "plt.tight_layout();"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "h7T0f1cqsaxA"
      },
      "source": [
        "### Вычисление градиентов"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tps0eWc0REqL"
      },
      "source": [
        "Теперь давайте посмотрим, как рассчитать градиенты, чтобы измерить взаимосвязь между изменениями в функции и изменениями в прогнозах модели. В случае изображений градиент сообщает нам, какие пиксели имеют наибольшее влияние на вероятности классов, предсказанные моделями."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ouuVIsdfgukW"
      },
      "source": [
        "$IntegratedGrads^{approx}_{i}(x)::=(x_{i}-x'_{i})\\times\\sum_{k=1}^{m}\\frac{\\overbrace{\\partial F(\\text{interpolated images})}^\\text{compute gradients}}{\\partial x_{i}} \\times \\frac{1}{m}$\n",
        "\n",
        "где:  \n",
        "$F()$ = ф-ция прогнозирования вашей модели  \n",
        "$\\frac{\\partial{F}}{\\partial{x_i}}$ = градиент(вектор частных производных $\\partial$) функции прогнозирования модели F относительно каждого признака $x_i$"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hY_Ok3CoJW1W"
      },
      "source": [
        "TensorFlow упрощает для вас вычисление градиентов с помощью [`tf.GradientTape`](https://www.tensorflow.org/api_docs/python/tf/GradientTape)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "JW1O9qEsxZOP"
      },
      "outputs": [],
      "source": [
        "def compute_gradients(images, target_class_idx):\n",
        "  with tf.GradientTape() as tape:\n",
        "    tape.watch(images)\n",
        "    logits = model(images)\n",
        "    probs = tf.nn.softmax(logits, axis=-1)[:, target_class_idx]\n",
        "  return tape.gradient(probs, images)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9BfRuzx4-c87"
      },
      "source": [
        "Давайте вычислим градиенты для каждого изображения вдоль пути интерполяции относительно правильного вывода. Напомним, что ваша модель возвращает тензор размерности `(1, 1001)` с логитами, которые вы конвертируете в предсказанные вероятности для каждого класса. Вам необходимо передать правильный индекс целевого класса ImageNet в функцию `compute_gradients`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "kHIR58rNJ3q_"
      },
      "outputs": [],
      "source": [
        "path_gradients = compute_gradients(\n",
        "    images=interpolated_images,\n",
        "    target_class_idx=555)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "JJodSbDUQ3T_"
      },
      "source": [
        "Обратите внимание на размерность вывода `(n_interpolated_images, img_height, img_width, RGB)`, которая дает нам градиент для каждого пикселя изображения вдоль пути интерполяции. Вы можете представить эти градиенты как величину изменения прогнозов вашей модели для каждого небольшого шага в пространстве функций."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "v2rpO2JTbQId"
      },
      "outputs": [],
      "source": [
        "print(path_gradients.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "zmaPpr5bUtbr"
      },
      "source": [
        "**Визуализация понижения градиента**\n",
        "\n",
        "Вспомните, что градиенты, которые вы только что вычислили выше, описывают *локальные* изменения в прогнозируемой вашей моделью вероятности «пожарной лодки» и могут *понижаться*.\n",
        "\n",
        "Эти концепции визуализируются на двух графиках ниже, с помощиью градиентов, рассчитанных выше ."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "mCH8sAf3TTJ2"
      },
      "outputs": [],
      "source": [
        "pred = model(interpolated_images)\n",
        "pred_proba = tf.nn.softmax(pred, axis=-1)[:, 555]\n",
        "\n",
        "plt.figure(figsize=(10, 4))\n",
        "ax1 = plt.subplot(1, 2, 1)\n",
        "ax1.plot(alphas, pred_proba)\n",
        "ax1.set_title('Target class predicted probability over alpha')\n",
        "ax1.set_ylabel('model p(target class)')\n",
        "ax1.set_xlabel('alpha')\n",
        "ax1.set_ylim([0, 1])\n",
        "\n",
        "ax2 = plt.subplot(1, 2, 2)\n",
        "# Усредняем по шагам интерполяции\n",
        "average_grads = tf.reduce_mean(path_gradients, axis=[1, 2, 3])\n",
        "# Нормализуем градиент до [0, 1]. Например (x - min(x))/(max(x)-min(x))\n",
        "average_grads_norm = (average_grads-tf.math.reduce_min(average_grads))/(tf.math.reduce_max(average_grads)-tf.reduce_min(average_grads))\n",
        "ax2.plot(alphas, average_grads_norm)\n",
        "ax2.set_title('Average pixel gradients (normalized) over alpha')\n",
        "ax2.set_ylabel('Average pixel gradients')\n",
        "ax2.set_xlabel('alpha')\n",
        "ax2.set_ylim([0, 1]);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-ntMpA87jNN6"
      },
      "source": [
        "* **слева**: этот график показывает, как степень уверенности вашей модели в классе «Fireboat» варьируется в зависимости от величины альфа. Обратите внимание на то, как градиенты уменьшаются и наклон линии сглаживается между 0,6 и 1,0, прежде чем значения установятся на вероятности «Fireboat» около 40%.\n",
        "\n",
        "* **справа**: правый график более точно показывает среднюю величину градиента. Обратите внимание, как значения резко приближаются к нулю и даже ненадолго опускаются ниже нуля. Фактически, ваша модель больше всего \"учится\" на градиентах при более низких значениях альфа. Это можно представить как ситуацию, когда ваша модель выучила пиксели, например водяных пушек, чтобы сделать правильный прогноз, подавив градиенты этих пикселей до нуля, но все еще довольно неопределенна и сфокусирована на ложных пикселях моста или водяной струи, поскольку значения альфа приближаются к исходному входному изображению.\n",
        "\n",
        "Чтобы убедиться, что пиксели водяной пушки определены как **важные** для прогноза «Fireboat», ниже вы узнаете как накапливать эти градиенты, чтобы оценить, как каждый пиксель влияет на тчоность узнавания «Fireboat»."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LQdACCM6sJdW"
      },
      "source": [
        "### Накопление градиентов (интегральное приближение)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QHopk9evmO5P"
      },
      "source": [
        "Есть много способов вычисления численного приближения интеграла для IG с различными компромиссами в точности и сходимости для различных функций. Популярный класс методов называется [суммы Римана](https://en.wikipedia.org/wiki/Riemann_sum). Здесь вы будете использовать правило трапеции(вы можете найти дополнительный код для изучения различных методов приближения в конце этого руководства)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "GshPZQgROs80"
      },
      "source": [
        "$IntegratedGrads^{approx}_{i}(x)::=(x_{i}-x'_{i})\\times \\overbrace{\\sum_{k=1}^{m}}^\\text{Sum m local gradients}\n",
        "\\text{gradients(interpolated images)} \\times \\overbrace{\\frac{1}{m}}^\\text{Divide by m steps}$\n",
        "\n",
        "Из уравнения вы можете видеть, что суммируете по `m` градиентов и делите на `m` шагов. Вы можете реализовать две операции 3-й части как *среднее значение локальных градиентов интерполированных предсказаний `m` и входных изображений*."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1cMVl-Grx3lp"
      },
      "outputs": [],
      "source": [
        "def integral_approximation(gradients):\n",
        "  # riemann_trapezoidal\n",
        "  grads = (gradients[:-1] + gradients[1:]) / tf.constant(2.0)\n",
        "  integrated_gradients = tf.math.reduce_mean(grads, axis=0)\n",
        "  return integrated_gradients"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QVQAHunkW79t"
      },
      "source": [
        "Функция `integral_approximation` берет градиенты предсказанной вероятности целевого класса по отношению к интерполированным изображениям между базовой линией и исходным изображением."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "JeF01fydNq0I"
      },
      "outputs": [],
      "source": [
        "ig = integral_approximation(\n",
        "    gradients=path_gradients)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7XVItLpurOAM"
      },
      "source": [
        "Вы можете убедиться, что усреднение по градиентам интерполированных изображений `m` возвращает интегрированный тензор градиентов той же размерности, что и исходное изображение."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "z1bP6l3ahfyn"
      },
      "outputs": [],
      "source": [
        "print(ig.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "C1ODXUevyGxL"
      },
      "source": [
        "### Собираем все вместе"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NcaTR-x8v1At"
      },
      "source": [
        "Теперь вы объедините три предыдущие части в функцию `IntegratedGradients` и воспользуетесь декоратором [@ tf.function](https://www.tensorflow.org/guide/function), чтобы скомпилировать его в высокопроизводительный граф TensorFlow. Это реализовано в виде 5 небольших шагов ниже:"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5YdWHscoovhk"
      },
      "source": [
        "$IntegratedGrads^{approx}_{i}(x)::=\\overbrace{(x_{i}-x'_{i})}^\\text{5.}\\times \\overbrace{\\sum_{k=1}^{m}}^\\text{4.} \\frac{\\partial \\overbrace{F(\\overbrace{x' + \\overbrace{\\frac{k}{m}}^\\text{1.}\\times(x - x'))}^\\text{2.}}^\\text{3.}}{\\partial x_{i}} \\times \\overbrace{\\frac{1}{m}}^\\text{4.}$"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pjdfjp_3pHiY"
      },
      "source": [
        "1. Создаем альфа $\\alpha$\n",
        "\n",
        "2. Генерируем интерполированные изображения = $(x '+ \\frac{k}{m}\\times(x - x'))$\n",
        "\n",
        "3. Вычисляем градиенты между предсказаниями выходных данных модели $F$ относительно входных признаков = $\\frac{\\partial F(\\text{интерполированные входные пути})}{\\partial x_{i}}$\n",
        "\n",
        "4. Делаем интегральное приближение через усреднение градиентов = $\\sum_{k=1}^m \\text{градиент} \\times \\frac{1}{m}$\n",
        "\n",
        "5. Масштабируем интегрированные градиенты относительно исходного изображения = $(x_{i}-x'_ {i}) \\times \\text {интегрированные градиенты}$. Причина, по которой этот шаг необходим, состоит в том, что нужно убедиться, что значения атрибуций, накопленные для нескольких интерполированных изображений, находятся в одной и той же размерности и точно отражают важность пикселей на исходном изображении."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "O_H3k9Eu7Rl5"
      },
      "outputs": [],
      "source": [
        "@tf.function\n",
        "def integrated_gradients(baseline,\n",
        "                         image,\n",
        "                         target_class_idx,\n",
        "                         m_steps=50,\n",
        "                         batch_size=32):\n",
        "  # 1. Создаем альфа\n",
        "  alphas = tf.linspace(start=0.0, stop=1.0, num=m_steps+1)\n",
        "\n",
        "  # Инициализируем TensorArray вне цикла для сбора градиентов.    \n",
        "  gradient_batches = tf.TensorArray(tf.float32, size=m_steps+1)\n",
        "    \n",
        "  # Итерируемся сразу по альфа и пакетам для улучшения производительности при больших m_steps.\n",
        "  for alpha in tf.range(0, len(alphas), batch_size):\n",
        "    from_ = alpha\n",
        "    to = tf.minimum(from_ + batch_size, len(alphas))\n",
        "    alpha_batch = alphas[from_:to]\n",
        "\n",
        "    # 2. Генерация интерполированных входных данных между базовой линией и входом.\n",
        "    interpolated_path_input_batch = interpolate_images(baseline=baseline,\n",
        "                                                       image=image,\n",
        "                                                       alphas=alpha_batch)\n",
        "\n",
        "    # 3. Вычисление градиентов между выходными данными модели и интерполированными входными данными.\n",
        "    gradient_batch = compute_gradients(images=interpolated_path_input_batch,\n",
        "                                       target_class_idx=target_class_idx)\n",
        "    \n",
        "    # Записываем пакеты индексов и градиентов во внешний TensorArray.\n",
        "    gradient_batches = gradient_batches.scatter(tf.range(from_, to), gradient_batch)    \n",
        "  \n",
        "  # Складываем градиенты пути построчно в один тензор.\n",
        "  total_gradients = gradient_batches.stack()\n",
        "\n",
        "  # 4. Интегральное приближение через усредненные градиенты.\n",
        "  avg_gradients = integral_approximation(gradients=total_gradients)\n",
        "\n",
        "  # 5. Масштабирование интегрированных градиентов относительно ввода.\n",
        "  integrated_gradients = (image - baseline) * avg_gradients\n",
        "\n",
        "  return integrated_gradients"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "8G0ELl_wRrd0"
      },
      "outputs": [],
      "source": [
        "ig_attributions = integrated_gradients(baseline=baseline,\n",
        "                                       image=img_name_tensors['Fireboat'],\n",
        "                                       target_class_idx=555,\n",
        "                                       m_steps=240)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "P-LSHD2sajFf"
      },
      "source": [
        "И снова, вы можете проверить, что атрибуты объекта IG имеют ту же размерность, что и входное изображение «Fireboat»."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "beoEunC5aZOa"
      },
      "outputs": [],
      "source": [
        "print(ig_attributions.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fLaB21txJhMq"
      },
      "source": [
        "В документе предлагается количество шагов в диапазоне от 20 до 300 в зависимости от примера(хотя на практике может потребоваться более 1000 шагов, чтобы точно аппроксимировать интеграл). Вы можете найти дополнительный код для проверки необходимого количества шагов в разделе «Следующие шаги» в конце этого руководства."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "o55W6NYXGSZ8"
      },
      "source": [
        "### Визуализация \"мышления\" модели"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "XSQ6Y-DZvrQu"
      },
      "source": [
        "Теперь вы готовы визуализировать процесс \"распознования признаков\" и наложить его на исходное изображение. В приведенном ниже коде суммируются абсолютные значения интегрированных градиентов по цветовым каналам для создания маски процесса распознования. Этот метод построения графика отражает относительное влияние пикселей на прогнозы модели."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4QN2cEA_WFym"
      },
      "outputs": [],
      "source": [
        "def plot_img_attributions(baseline,\n",
        "                          image,\n",
        "                          target_class_idx,\n",
        "                          m_steps=50,\n",
        "                          cmap=None,\n",
        "                          overlay_alpha=0.4):\n",
        "\n",
        "  attributions = integrated_gradients(baseline=baseline,\n",
        "                                      image=image,\n",
        "                                      target_class_idx=target_class_idx,\n",
        "                                      m_steps=m_steps)\n",
        "\n",
        "  # Сумма атрибутов по цветовым каналам для визуализации.\n",
        "  # Форма маски - это изображение в оттенках серого, \n",
        "  # высота и ширина которого равны исходному изображению.\n",
        "  attribution_mask = tf.reduce_sum(tf.math.abs(attributions), axis=-1)\n",
        "\n",
        "  fig, axs = plt.subplots(nrows=2, ncols=2, squeeze=False, figsize=(8, 8))\n",
        "\n",
        "  axs[0, 0].set_title('Baseline image')\n",
        "  axs[0, 0].imshow(baseline)\n",
        "  axs[0, 0].axis('off')\n",
        "\n",
        "  axs[0, 1].set_title('Original image')\n",
        "  axs[0, 1].imshow(image)\n",
        "  axs[0, 1].axis('off')\n",
        "\n",
        "  axs[1, 0].set_title('Attribution mask')\n",
        "  axs[1, 0].imshow(attribution_mask, cmap=cmap)\n",
        "  axs[1, 0].axis('off')\n",
        "\n",
        "  axs[1, 1].set_title('Overlay')\n",
        "  axs[1, 1].imshow(attribution_mask, cmap=cmap)\n",
        "  axs[1, 1].imshow(image, alpha=overlay_alpha)\n",
        "  axs[1, 1].axis('off')\n",
        "\n",
        "  plt.tight_layout()\n",
        "  return fig"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "n73VxzbxeMvD"
      },
      "source": [
        "Глядя на объекты характеристик модели на изображении «Пожарная лодка», вы можете увидеть, что модель определяет водометы и струи как способствующие ее правильному прогнозу."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "vxCQFx96iDVs"
      },
      "outputs": [],
      "source": [
        "_ = plot_img_attributions(image=img_name_tensors['Fireboat'],\n",
        "                          baseline=baseline,\n",
        "                          target_class_idx=555,\n",
        "                          m_steps=240,\n",
        "                          cmap=plt.cm.inferno,\n",
        "                          overlay_alpha=0.4)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Lo4SncDZfTw0"
      },
      "source": [
        "На изображении «Гигантская панда» объектами характеристик являются текстуры, нос и мех лица панды.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "TcpGLJWuHnYl"
      },
      "outputs": [],
      "source": [
        "_ = plot_img_attributions(image=img_name_tensors['Giant Panda'],\n",
        "                          baseline=baseline,\n",
        "                          target_class_idx=389,\n",
        "                          m_steps=55,\n",
        "                          cmap=plt.cm.viridis,\n",
        "                          overlay_alpha=0.5)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "H3etJZHuI6hX"
      },
      "source": [
        "## Использование и ограничения"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xEX4Jh2uLvxA"
      },
      "source": [
        "Сценарии использования\n",
        "* Использование таких методов, как интегрированные градиенты, перед развертыванием модели может дать вам понимание того, как и почему работает модель. Соответствуют ли признаки, выделенные с помощью этой техники, вашему пониманию? В противном случае это может указывать на ошибку в вашей модели или наборе данных, или на переобучение.\n",
        "\n",
        "Ограничения\n",
        "* Интегрированные градиенты обеспечивают важность признаков на отдельных примерах, однако не предоставляют глобальную важность признаков для всего набора данных.\n",
        "\n",
        "* Интегрированные градиенты предоставляют индивидуальные значения важности признаков, но не объясняют их взаимодействие и комбинации."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Ejc2Ho_8i162"
      },
      "source": [
        "## Следующие шаги"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "G8Ra5ijj7pEc"
      },
      "source": [
        "В этом руководстве представлена базовая реализация интегрированных градиентов. В качестве следующего шага вы можете использовать этот ноутбук, чтобы самостоятельно опробовать эту технику с разными моделями и изображениями.\n",
        "\n",
        "Для заинтересованных читателей есть расширенная версия этого руководства (которая включает код для различных базовых показателей, для вычисления интегральных приближений и определения достаточного количества шагов), которую вы можете найти [здесь](https://github.com/GoogleCloudPlatform/training-data-analyst/tree/master/blogs/Integrated_gradients).\n",
        "\n",
        "Чтобы углубить свое понимание интегрированных градиентов, ознакомьтесь со статьей [Axiomatic Attribution for Deep Networks](https://arxiv.org/abs/1703.01365) и [Github репозиторием](https://github.com/ankurtaly/Integrated-Gradients), который содержит реализацию в предыдущей версии TensorFlow. Вы также можете изучить атрибуцию признаков и влияние различных базовых показателей на [distill.pub](https://distill.pub/2020/attribution-baselines/).\n",
        "\n",
        "Заинтересованы во внедрении IG в производственные рабочие процессы машинного обучения для определения важности функций, анализа ошибок модели и мониторинга перекоса данных? Ознакомьтесь с продуктом Google Cloud [Explainable AI](https://cloud.google.com/explainable-ai), который поддерживает атрибуцию IG. Исследовательская группа Google AI PAIR также открыла исходный код [What-if tool](https://pair-code.github.io/what-if-tool/index.html#about), который можно использовать для отладки модели, включая визуализацию \"образа мышления\" модели IG."
      ]
    }
  ],
  "metadata": {
    "accelerator": "GPU",
    "colab": {
      "collapsed_sections": [],
      "name": "integrated_gradients.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
